using System.Diagnostics;
using System.Windows;
using Microsoft.Practices.CompositeUI;
using Microsoft.Practices.CompositeUI.Utility;

namespace SCSFContrib.CompositeUI.WPF.Activation
{
	/// <summary>
	/// An implementation of <see cref="IFrameworkContentElementActivationService"/> that monitors a <see cref="FrameworkContentElement"/>
	/// and activates and deactivates a <see cref="WorkItem"/> when the <see cref="FrameworkContentElement"/> gains or loses focus.
	/// </summary>
	/// <remarks>
	/// <para>
	/// The <c>FrameworkContentElementActivationService</c> is responsible for activating <see cref="WorkItem"/>s as the user navigates
	/// around a CAB application. Essentially, as <see cref="FrameworkContentElement"/>s receive keyboard focus, this service will ensure
	/// the correct <see cref="WorkItem"/> is activated.
	/// </para>
	/// <para>
	/// Sometimes it is necessary to ensure that focusing on certain elements does not cause the related <see cref="WorkItem"/> to be activated.
	/// For example, this is most often true menu items. The <c>IsExempt</c> attached property allows specific
	/// <see cref="FrameworkContentElement"/>s to receive focus without affecting the currently activated <see cref="WorkItem"/>.
	/// </para>
	/// </remarks>
	public sealed class FrameworkContentElementActivationService : IFrameworkContentElementActivationService
	{
		private readonly WorkItem _workItem;

		/// <summary>
		/// Identifies the <c>IsExempt</c> attached property.
		/// </summary>
		public static readonly DependencyProperty IsExemptProperty = DependencyProperty.RegisterAttached("IsExempt",
			typeof(bool),
			typeof(FrameworkContentElementActivationService));

		/// <summary>
		/// Constructs an instance of <c>FrameworkContentElementActivationService</c>.
		/// </summary>
		/// <param name="workItem">
		/// The <see cref="WorkItem"/> in which this service resides.
		/// </param>
		public FrameworkContentElementActivationService([ServiceDependency] WorkItem workItem)
		{
			Guard.ArgumentNotNull(workItem, "workItem");
			_workItem = workItem;
		}

		private delegate void FrameworkContentElementHandler(FrameworkContentElement frameworkContentElement);

		/// <summary>
		/// See <see cref="IFrameworkContentElementActivationService.FrameworkContentElementAdded"/>.
		/// </summary>
		public void FrameworkContentElementAdded(FrameworkContentElement frameworkContentElement)
		{
			Guard.ArgumentNotNull(frameworkContentElement, "frameworkContentElement");

			if (!frameworkContentElement.Dispatcher.CheckAccess())
			{
				frameworkContentElement.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Send, new FrameworkContentElementHandler(FrameworkContentElementAdded), frameworkContentElement);
			}
			else
			{
				frameworkContentElement.Loaded += frameworkContentElement_Loaded;
				frameworkContentElement.Unloaded += frameworkContentElement_Unloaded;
			}
		}

		/// <summary>
		/// See <see cref="IFrameworkContentElementActivationService.FrameworkContentElementRemoved"/>.
		/// </summary>
		public void FrameworkContentElementRemoved(FrameworkContentElement frameworkContentElement)
		{
			Guard.ArgumentNotNull(frameworkContentElement, "frameworkContentElement");

			if (!frameworkContentElement.Dispatcher.CheckAccess())
			{
				frameworkContentElement.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Send, new FrameworkContentElementHandler(FrameworkContentElementRemoved), frameworkContentElement);
			}
			else
			{
				frameworkContentElement.Loaded -= frameworkContentElement_Loaded;
				frameworkContentElement.Unloaded -= frameworkContentElement_Unloaded;
			}
		}

		/// <summary>
		/// Gets a value indicating whether the specified <see cref="FrameworkContentElement"/> is exempt from special treatment
		/// by this <c>FrameworkContentElementActivationService</c>.
		/// </summary>
		/// <param name="frameworkContentElement">
		/// The <see cref="FrameworkContentElement"/> in question.
		/// </param>
		/// <returns>
		/// <see langword="true"/> if <paramref name="frameworkContentElement"/> is exempt from special treatment, otherwise
		/// <see langword="false"/>.
		/// </returns>
		public static bool GetIsExempt(FrameworkContentElement frameworkContentElement)
		{
			Guard.ArgumentNotNull(frameworkContentElement, "frameworkContentElement");
			return (bool)frameworkContentElement.GetValue(IsExemptProperty);
		}

		/// <summary>
		/// Sets a value indicating whether a given <see cref="FrameworkContentElement"/> should be ignored by this
		/// <c>FrameworkContentElementActivationService</c>.
		/// </summary>
		/// <param name="frameworkContentElement">
		/// The <see cref="FrameworkContentElement"/> in question.
		/// </param>
		/// <param name="value">
		/// <see langword="true"/> to ignore focus events raised by <paramref name="frameworkContentElement"/>.
		/// </param>
		public static void SetIsExempt(FrameworkContentElement frameworkContentElement, bool value)
		{
			Guard.ArgumentNotNull(frameworkContentElement, "frameworkContentElement");
			frameworkContentElement.SetValue(IsExemptProperty, value);
		}

		private void frameworkContentElement_Loaded(object sender, RoutedEventArgs e)
		{
			Debug.Assert(sender is FrameworkContentElement);
			(sender as FrameworkContentElement).GotKeyboardFocus += frameworkContentElement_GotKeyboardFocus;
		}

		private void frameworkContentElement_Unloaded(object sender, RoutedEventArgs e)
		{
			Debug.Assert(sender is FrameworkContentElement);
			(sender as FrameworkContentElement).GotKeyboardFocus -= frameworkContentElement_GotKeyboardFocus;
		}

		private void frameworkContentElement_GotKeyboardFocus(object sender, RoutedEventArgs e)
		{
			Debug.Assert(sender is FrameworkContentElement);
			e.Handled = ActivateWorkItemForFrameworkContentElement(sender as FrameworkContentElement);
		}

		private bool ActivateWorkItemForFrameworkContentElement(FrameworkContentElement frameworkContentElement)
		{
			Debug.Assert(frameworkContentElement != null);

			if (GetIsExempt(frameworkContentElement))
			{
				return false;
			}

			_workItem.Activate();
			return true;
		}
	}
}
